package net.brucecooper.mindmap.client;

import gwt.canvas.client.Canvas;
import gwt.canvas.client.ImageListener;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.ContextMenuHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.XMLParser;

class IconInfo {
	String name;
	String iconURL;
	
	public IconInfo(String name, String iconURL) {
		this.name = name;
		this.iconURL = iconURL;
	}
}


public class MindMap extends Composite implements NodeDataStoreListener, ImageListener {
    private static final String UPLOAD_HOST = "2.latest.cactus-wave.appspot.com"; // TODO change this back to the normal URL when you release.
    enum DragStatus {NONE, WITHIN_THRESHOLD, DRAGGING};
    private HorizontalPanel buttonBar;
    private NodeSelectionManager selectionManager = new NodeSelectionManager();
    private NodeDataStore dataStore;
    private NodeWidget rootWidget;
    private int lastWidth = -1;
    private int deferredEditID;
    private Timer scrollerWidthUpdateTimer;
    private List<SizeChangeCallback> sizeChangeListeners = new ArrayList<SizeChangeCallback>();
    private DialogBox uploadDialog;
    private HandlerRegistration dragHandler;
    private boolean editOnMouseUp;
    private DragStatus dragStatus = DragStatus.NONE;
    private boolean firstLayout = true;
    private ScrollingCanvas scanv = null;
    private VerticalPanel vp = new VerticalPanel();
    private PopupPanel contextPopup;

    private IconInfo[] icons = new IconInfo[] {
    	new IconInfo("Question", "help"),
    	new IconInfo("Important", "messagebox_warning"),
    	new IconInfo("Idea", "idea"),
    	new IconInfo("Ok", "button_ok"),
    	new IconInfo("not OK", "button_cancel"),
    	new IconInfo("Priority 1", "full-1"),
    	new IconInfo("Priority 2", "full-2"),
    	new IconInfo("Priority 3", "full-3"),
    	new IconInfo("Priority 4", "full-4"),
    	new IconInfo("Priority 5", "full-5"),
    	new IconInfo("Priority 6", "full-6"),
    	new IconInfo("Priority 7", "full-7"),
    	new IconInfo("Back", "back"),
    	new IconInfo("Forward", "forward"),
    	new IconInfo("Look Here", "attach"),
    	new IconInfo("I am Happy", "ksmiletris"),
    	new IconInfo("Dangerous", "clanbomber"),
    	new IconInfo("Do Not Forget", "desktop_new"),
    	new IconInfo("Flag", "flag"),
    	new IconInfo("Home", "gohome"),
    	new IconInfo("Phone", "kaddressbook"),
    	new IconInfo("Music", "knotify"),
    	new IconInfo("Mailbox", "korn"),
    	new IconInfo("Mail", "Mail"),
    	new IconInfo("Key", "password"),
    	new IconInfo("To be refined", "pencil"),
    	new IconInfo("Stop", "stop"),
    	new IconInfo("Magic", "wizard"),
    	new IconInfo("To be discussed", "xmag"),
    	new IconInfo("Remember", "bell"),
    	new IconInfo("Excelent", "bookmark"),
    	new IconInfo("Linux", "penguin"),
    	new IconInfo("Nice", "licq")
    };

    private NodeWidget draggedNode;
    private PopupPanel dragPopup;
    private NodeWidget dropNode;
    private UserInfoFetcher userInfoFetcher = new DummyUserInfoFetcher();
    private Map<Integer, Boolean> collapsedNodes = new HashMap<Integer, Boolean>();
	private boolean inLayout;
    MouseRedirector mouseEventRedirector = new MouseRedirector();
	private Timer tooltipTimer;
	private GraphicComponent tooltipComponent;
	private PopupPanel tooltipPopup;
	private int popupY;
	private int popupX;
    private PopupPanel iconPopup;

	
	
    Action newChildAction      = new Action("New Child", "16-square-green-add.png") {
        public void onClick(ClickEvent event) {
            addNewChild();
        }
    };
    Action newSiblingAction    = new Action("New Sibling", "16-square-blue-add.png") {
        public void onClick(ClickEvent event) {
            addNewSibling();
        }
    };

    Action deleteAction        = new Action("Delete",      "16-em-cross.png") {
        public void onClick(ClickEvent event) {
            deleteSelectedNode();
        }
    };

    Action downloadAction      = new Action("Download Freemind Map",    "16-em-down.png") {
        public void onClick(ClickEvent event) {
            downloadMindmap();
        }
    };

    Action uploadAction        = new Action("Upload Freemind Map",  "16-em-open.png") {
        public void onClick(ClickEvent event) {
            final FormPanel form = new FormPanel();
            final String uploadID = UUID.uuid();

            form.setAction("http://" + UPLOAD_HOST  + "/upload.mm");
            form.setEncoding(FormPanel.ENCODING_MULTIPART);
            form.setMethod(FormPanel.METHOD_POST);

            form.addSubmitCompleteHandler(new SubmitCompleteHandler() {
                public void onSubmitComplete(SubmitCompleteEvent event) {
                    requestModelChangeFromURL(MindMap.this, "http://" + UPLOAD_HOST + "/upload.mm?id="+uploadID);
                }

            });
            Hidden id = new Hidden("id", uploadID);
            FileUpload upload = new FileUpload();
            upload.setName("contents");

            FlowPanel fp = new FlowPanel();
            fp.add(id);
            fp.add(upload);
            form.add(fp);
            
            
            uploadDialog = new OptionDialog("Upload FreeMind File", form, new String[] {"Upload", "Cancel" }, new OptionSelectionHandler() {
                public boolean optionSelected(int optionNumber) {
                    switch (optionNumber) {
                    case 0: // Upload 
                        form.submit();
                        return false;  //  Don't hide the dialog, it will be done automatically when the form processes.
                    default:
                        return true;
                    }
                }
            });

            uploadDialog.setPopupPosition((MindMap.this.getOffsetWidth() - 200) / 2, 50);
            uploadDialog.show();
        }
    };

    Action setIconAction = new Action("Set Icon",    "bookmark.png") {
        public void onClick(ClickEvent event) {
            iconPopup.showRelativeTo((UIObject) event.getSource());
        }
    };

    Action voteUpAction         = new Action("Vote Up",  "vote_yes.png") {
        public void onClick(ClickEvent event) {
            voteSelectedNode(true);
        }
    };

    Action voteDownAction       = new Action("Vote Down",  "vote_no.png") {
        public void onClick(ClickEvent event) {
            voteSelectedNode(false);
        }
    };
    Action propertiesAction      = new Action("Edit Properties",    "pencil.png") {
        public void onClick(ClickEvent event) {
            showProperties();
        }
    };


   Action[] actions = new Action[] {
           newChildAction,
           newSiblingAction,
           deleteAction,
           downloadAction,
           uploadAction,
           setIconAction,
           voteUpAction,
           voteDownAction,
           propertiesAction
    };

	
	
	

    public MindMap(NodeDataStore dataStore) {
    	IconManager.setListener(this);
    	
        // Log.debug("Creating MindMap");
        this.dataStore = dataStore;
        dataStore.addNodeDataStoreListener(this);
        buttonBar = new HorizontalPanel();

        
        scanv = new ScrollingCanvas(200, 200) {
            @Override
            protected void paint(Canvas canvas) {
                getRootWidget().paint(canvas, new Clip(clipX1, clipY1, clipX2, clipY2));
            }
        };
        
//        canvas = new Canvas();
        scanv.addMouseDownHandler(mouseEventRedirector );
        scanv.addMouseUpHandler(mouseEventRedirector);
        scanv.addMouseMoveHandler(mouseEventRedirector);
        scanv.addContextMenuHandler(mouseEventRedirector);
//        canvas.addClickHandler(mouseEventRedirector);
        
        selectionManager.addSelectionListener(new NodeSelectionListener() {
            public void nodeSelectionChanged() {
                if (!inLayout) {
                    repaint();
                }
            }
        });
        
        
        scanv.addKeyPressHandler(new KeyPressHandler() {

            public void onKeyPress(KeyPressEvent event) {
                event.stopPropagation();
                event.preventDefault();
                NativeEvent evt = event.getNativeEvent();
                boolean shift = evt.getShiftKey();
                int code = evt.getKeyCode();
                // boolean ctrl = evt.getCtrlKey();

                // Log.debug("Pressed " + code);

                switch (code) {
                case 9:
                    if (shift) {
                        addNewSibling();
                    } else {
                        addNewChild();
                    }
                    break;
                case 105:
                    addNewChild();
                    break;
                case 73:
                    addNewSibling();
                    break;

                // Up
                case 107:
                case 63232:
                    selectNextWidgetUp();
                    break;
                // Down
                case 106:
                case 63233:
                    selectNextWidgetDown();
                    break;
                // Left
                case 104:
                case 63234:
                    selectNextWidgetLeft();
                    break;
                // Right
                case 108:
                case 63235:
                    selectNextWidgetRight();
                    break;

                // DEL
                case 120:
                case 63272:
                    // case 8:
                    deleteSelectedNode();
                    break;
                case 99:
                case 101:
                    // case 8:
                    
                    int sel = selectionManager.getSelected();
                    if (sel != -1) {
                        getNodeWidget(sel).startEditing();
                    }
                    break;

                default:
                    // System.out.println(shift + "/" + ctrl + "/" + code);
                }

            }

        });

        
        iconPopup = new PopupPanel(true);
        iconPopup.addStyleName("iconsPopup");
        final Grid iconsPanel = new Grid(icons.length/2 + (icons.length%2), 2);

        Anchor noneAnchor = new Anchor("None", true);
        noneAnchor.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                setIconOnSelectedNode(null);
                iconPopup.hide();
            }
        });
        iconsPanel.setWidget(0,0, noneAnchor);

        int count = 1;
        for (IconInfo icon: icons) {
        	final IconInfo finalIcon = icon;
        	Anchor a = new Anchor(IconManager.makeImgHTML(icon.name,  icon.iconURL + ".png") + icon.name, true);
        	a.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {
					setIconOnSelectedNode(finalIcon.iconURL);
					iconPopup.hide();
				}
        	});
        	iconsPanel.setWidget(count/2, count%2, a);
        	count++;
        }
        iconPopup.add(iconsPanel);
        
        for (Action action: actions) {
            buttonBar.add(action.createButton());
        }

        // This is a nominal width. It'll get updated by the timer.
        scanv.setWidth("650px");
        scrollerWidthUpdateTimer = new Timer() {
            @Override
            public void run() {
                checkScrollerWidth();
            }
        };

        // Schedule the timer to run once a second.
        scrollerWidthUpdateTimer.scheduleRepeating(1000);

        vp.add(buttonBar);
        vp.add(scanv);
        initWidget(vp);
        setWidth("100%");
        
        RootPanel.get().add(this);
        layout();
    }

    
    protected void showProperties() {
        final NodePropertiesPanel npp = new NodePropertiesPanel(dataStore, getSelectionManager().getSelected());
        uploadDialog = new OptionDialog("Node Properties", npp, new String[] {"Update", "Cancel" }, new OptionSelectionHandler() {
            public boolean optionSelected(int optionNumber) {
                if (optionNumber == 0) {
                    npp.updateDataStore();
                    layout();
                }
                return true;
            }
        });

        uploadDialog.setPopupPosition((MindMap.this.getOffsetWidth() - 200) / 2, 50);
        uploadDialog.show();
        
    }


    @Override
    public void onBrowserEvent(Event evt) {
        doLog("Browser event " + evt);
        super.onBrowserEvent(evt);
    }
    
    private static void doLog(String message) {
        GWT.log(message, null);
        doLogNative(message);
    }
    
    private static native void doLogNative(String message)/*-{
        console.log(message);
    }-*/;
    
    public NodeWidget getNodeWidget(int nodeID) {
        return rootWidget.getNodeWidget(nodeID);
    }
    
    public NodeWidget getNodeWidgetParent(GraphicComponent comp) {
    	GraphicComponent c = comp;
    	while (c != null && !(c instanceof NodeTextWidget)) {
    		c = c.getParent();
    	}
    	if (c != null) {
    		return ((NodeTextWidget) c).getNodeWidget();
    	}
		return null;
	}


	public void stopTooltipTimer() {
		if (this.tooltipTimer != null) {
			this.tooltipTimer.cancel();
			this.tooltipTimer = null;
			this.tooltipComponent = null;
		}
		if (tooltipPopup != null) {
			tooltipPopup.hide();
			tooltipPopup = null;
		}
	}


	public void resetTooltipTimer(GraphicComponent comp, int x, int y) {
		if (tooltipTimer == null || comp != this.tooltipComponent) {
			this.tooltipComponent = comp;
			this.popupX = x;
			this.popupY = y;
	        this.tooltipTimer = new Timer() {
	            @Override
	            public void run() {
	                showTooltip();
	                tooltipTimer = null;
	            }
	        };
	        tooltipTimer.schedule(750);
		}
	}


	protected void showTooltip() {
		this.tooltipTimer = null;

		if (tooltipPopup != null) {
			tooltipPopup.hide();
			tooltipPopup = null;
		}

		
		this.tooltipPopup = new PopupPanel(true);
		this.tooltipPopup.setStylePrimaryName("tooltip");
		tooltipPopup.setWidget(tooltipComponent.getTooltip());
		tooltipPopup.setPopupPosition(popupX, popupY);
		tooltipPopup.show();
		
		this.tooltipComponent = null;
	}


	private GraphicComponent findDeepest(MouseEvent<?> e) {
		return findDeepest(e.getX(), e.getY());
	}
	
	private GraphicComponent findDeepest(int x, int y) {
		NodeWidget root = getRootWidget();
        return root == null ? null : root.findDeepest(x, y);
    }



    private void downloadMindmap() {
        final String uploadID = UUID.uuid();
        String encoded = dataStore.encodeModel();
        doPost(this, "http://" + UPLOAD_HOST + "/download.mm", uploadID, encoded);
    }
    
    private native void doPost(MindMap mm, String url, String uuid, String content) /*-{
      var params = {};
      var data = {
          id : uuid,
          contents : content
        };
 
      params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;
      params[gadgets.io.RequestParameters.POST_DATA] = gadgets.io.encodeValues(data);
      gadgets.io.makeRequest(url, function(obj) {
          $wnd.location = "http://2.latest.cactus-wave.appspot.com/download.mm?id="+uuid; 
      }, params);
    }-*/;

    
    
    // TODO this will only work in the gadget container.  Turn this into an
    // interface with multiple implementations.
	private native void requestModelChangeFromURL(MindMap mm, String url)/*-{
      var params = {};
      params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
      gadgets.io.makeRequest(url, function(obj) {
          var s = obj.text;
      	  mm.@net.brucecooper.mindmap.client.MindMap::replaceModel(Ljava/lang/String;)(s);
      }, params);
	}-*/;

    public void replaceModel(String text) {
        Document doc = XMLParser.parse(text);
        
		
		dataStore.replaceModel(doc);
		uploadDialog.hide();
		uploadDialog = null;
	}

    private void checkScrollerWidth() {
        int newWidth = getOffsetWidth() - 20;
        if (newWidth < 200)
            newWidth = 200;
        if (lastWidth != newWidth) {
            scanv.setWidth(newWidth + "px");
            layout();
            lastWidth = newWidth;
        }
    }

    public void addSizeChangeListener(SizeChangeCallback listener) {
        sizeChangeListeners.add(listener);
    }

    protected void fireSizeChanged() {
        for (SizeChangeCallback cb : sizeChangeListeners) {
            cb.sizeMayHaveChanged();
        }
    }
    
    private void repaint() {
        scanv.repaint();
//        canvas.clear();
//        getRootWidget().paint(canvas);
    }
    
    

    public void layout() {
    	inLayout = true;
    	try {
	        // Create a widget to represent the root node.
	        setRootWidget(new NodeWidget(this, null, dataStore, NodeDataStore.ROOT_NODE, dataStore.getRootNodeOrientation(), 0));
	        Canvas layoutCanvas = scanv.getLayoutCanvas();
	        getRootWidget().layout(layoutCanvas);
	        getRootWidget().setBounds(0,0, getRootWidget().getPreferredWidth(layoutCanvas), getRootWidget().getPreferredHeight(layoutCanvas));
	        
	        
	        int width = getRootWidget().getPreferredWidth(layoutCanvas)+5;
	        int height = Math.max(200, getRootWidget().getPreferredHeight(layoutCanvas)+5);
	        
	        scanv.setCanvasSize(width, height);
	        
//	        layoutPanel.setWidth(width + "px");
//	        layoutPanel.setHeight(height + "px");
//	        canvas.setWidth(width);
//	        canvas.setHeight(height);

	        repaint();
	
	        fireSizeChanged();
	        if (firstLayout) {
	            firstLayout = false;
	        }
	    }finally {
	    	inLayout = false;
        }
    }

	private void setIconOnSelectedNode(String newIcon) {
        int sel = selectionManager.getSelected();

        if (sel != -1 ) {
        	dataStore.setIcon(sel, newIcon);
        }
	}

    
    private void deleteSelectedNode() {
        int sel = selectionManager.getSelected();

        if (sel != -1 && sel != NodeDataStore.ROOT_NODE) {
            int parentNode = getNodeWidget(sel).getParentNodeWidget().getNodeID();

            dataStore.removeNodeFrom(parentNode, sel);
            layout();
            selectionManager.select(parentNode);
            scanv.setFocus(true);
        }

    }
    
    public UserInfoFetcher getUserInfoFetcher() {
        return userInfoFetcher ;
    }
    
    public void setUserInfoFetcher(UserInfoFetcher waveUserInfoFetcher) {
        this.userInfoFetcher = waveUserInfoFetcher;
    }

    private void voteSelectedNode(boolean up) {
        int sel = selectionManager.getSelected();

        if (sel != -1) {
            dataStore.voteForNode(sel, getUserInfoFetcher().getCurrentUser(), up);
            scanv.setFocus(true);
        }

    }
    
    private void selectNextWidgetUp() {
        int sel = selectionManager.getSelected();

        if (sel != -1) {
            NodeWidget w = getNodeWidget(sel);
            NodeWidget parent = w.getParentNodeWidget();
            if (parent != null) {
                NodeWidget next = parent.getPreviousSibling(w);
                if (next == null)
                    next = parent;
                selectionManager.select(next.getNodeID());
            }
        }
    }

    private void selectNextWidgetDown() {
        int sel = selectionManager.getSelected();

        if (sel != -1) {
            NodeWidget w = getNodeWidget(sel);
            NodeWidget parent = w.getParentNodeWidget();
            if (parent != null) {
                NodeWidget next = parent.getNextSibling(w);
                if (next == null)
                    next = parent;
                selectionManager.select(next.getNodeID());
            }
        }
    }

    private void selectNextWidgetLeft() {
        int sel = selectionManager.getSelected();

        if (sel != -1) {
            NodeWidget w = getNodeWidget(sel);
            NodeWidget newWidget = null;
            switch (w.getOrientation()) {
            case LEFT:
            case CENTER:
                newWidget = w.getFirtLeftChild();
                break;
            case RIGHT:
                newWidget = w.getParentNodeWidget();
                break;
            }

            if (newWidget != null) {
                selectionManager.select(newWidget.getNodeID());
            }
        }
    }

    private void selectNextWidgetRight() {
        int sel = selectionManager.getSelected();

        if (sel != -1) {
            NodeWidget w = getNodeWidget(sel);
            NodeWidget newWidget = null;
            switch (w.getOrientation()) {
            case RIGHT:
            case CENTER:
                newWidget = w.getFirtRightChild();
                break;
            case LEFT:
                newWidget = w.getParentNodeWidget();
                break;
            }

            if (newWidget != null) {
                selectionManager.select(newWidget.getNodeID());
            }
        }
    }

    private void addNewSibling() {
        int sel = selectionManager.getSelected();

        if (sel != -1) {
            NodeWidget w = getNodeWidget(sel);
            NodeWidget parent = w.getParentNodeWidget();

            if (parent != null) {
                selectionManager.select(parent.getNodeID());
                addNewChild();
            } else {
                // WE're already at the root, so just add a new child.
                addNewChild();
            }
        }

    }

    private void addNewChild() {
        int sel = selectionManager.getSelected();

        
        if (sel != -1 && dataStore.isReady()) {
            int newNodeID = dataStore.addChildTo(sel);
            NodeWidget nw = getRootWidget().getNodeWidget(newNodeID);
            if (nw == null) {
                // By setting the deferredEditNode, we react to the addition of
                // this
                // element
                deferredEditID = newNodeID;
            } else {
                nw.startEditing();
            }

        }
    }

    public ScrollingCanvas getCanvas() {
        return scanv;
    }

    public void childAdded(NodeDataStoreEvent evt) {
        layout();

        if (evt.getChildNode() == deferredEditID) {
            deferredEditID = -1;
            NodeWidget nw = getRootWidget().getNodeWidget(evt.getChildNode());
            nw.startEditing();
        }
    }

    public void childDeleted(NodeDataStoreEvent evt) {
        if (evt.getChildNode() == selectionManager.getSelected()) {
            selectionManager.select(evt.getNode());
        }
        layout();
    }

    public void nodeChanged(NodeDataStoreEvent evt) {
        layout();
    }
    
	public void modelChanged(NodeDataStoreEvent e) {
		layout();
	}
	
	private boolean isDragging() {
	    return dragHandler != null;
	}
	private void cancelDrag() {
	    if (dragHandler != null) {
    	    dragHandler.removeHandler();
    	    dragHandler = null;
	    }
	    if (dragPopup != null) {
	        dragPopup.hide();
	        dragPopup = null;
	    }
	    if (draggedNode != null) {
	        draggedNode.setDropProhibited(false);
	    }
	    dragStatus = DragStatus.NONE;
	    editOnMouseUp = false;
	    draggedNode = null;
        setDropTarget(null);
        repaint();
	}

    private void startDragging(MouseEvent<?> e, final int xOffset, final int yOffset) {
        dragStatus = DragStatus.DRAGGING;
        dragPopup = new PopupPanel();
        dragPopup.setStylePrimaryName("dragwidget");

        dragPopup.addStyleName("drag");
        
        
        NodeTextWidget dragComp = new NodeTextWidget(draggedNode);
        Canvas dragCanvas = new Canvas();
        dragComp.layout(dragCanvas);
        dragComp.setBounds(2,2,dragComp.getPreferredWidth(dragCanvas)+4, dragComp.getPreferredHeight(dragCanvas)+2);
        dragCanvas.setWidth(dragComp.getWidth());
        dragCanvas.setHeight(dragComp.getHeight());
        dragComp.paint(dragCanvas, new Clip(0,0, dragComp.getWidth(), dragComp.getHeight()));
        
        dragCanvas.addMouseMoveHandler(new MouseMoveHandler() {
            public void onMouseMove(MouseMoveEvent e) {
                updateDragging(scanv.getRelativeX(e), scanv.getRelativeY(e), e.getClientX(), e.getClientY() , xOffset, yOffset);
            }
        });
        
        dragCanvas.addMouseUpHandler(new MouseUpHandler() {
            public void onMouseUp(MouseUpEvent event) {
                finishDrag();
            }
            
        });
        
        
        dragPopup.add(dragCanvas);
        draggedNode.setDropProhibited(true);
        
        dragPopup.show();
        updateDragging(e.getX(), e.getY(), e.getClientX(), e.getClientY() , xOffset, yOffset);


    }
    
    private void finishDrag() {
        if (dropNode != null) {
            dataStore.changeNodeParent(draggedNode.getNodeID(), draggedNode.getParentNodeWidget().getNodeID(), dropNode.getNodeID());
        }
        cancelDrag();
    }

    private void updateDragging(int canvasX, int canvasY, int clientX, int clientY, int xOffset, int yOffset) {
        dragStatus = DragStatus.DRAGGING;
        dragPopup.setPopupPosition(clientX-xOffset, clientY-yOffset + Window.getScrollTop());
        
        GraphicComponent comp = findDeepest(canvasX, canvasY);
    	NodeWidget dropNode = getNodeWidgetParent(comp);
        if (dropNode != null && dropNode.isDropProhibited()) {
            dropNode = null;
        }

        if (dropNode != null) {
            dragPopup.removeStyleName("dragInactive");
            dragPopup.addStyleName("dragActive");
        } else {
            dragPopup.removeStyleName("dragActive");
            dragPopup.addStyleName("dragInactive");
        }
        setDropTarget(dropNode);
        repaint();

    }
	
    private void setDropTarget(NodeWidget dropNode) {
        if (this.dropNode != dropNode) {
            if (this.dropNode != null) {
                this.dropNode.setDropTarget(false);
            }
            this.dropNode = dropNode;
            if (this.dropNode != null) {
                this.dropNode.setDropTarget(true);
            }
            repaint();
        }
    }


    public void registerForDrag(NodeWidget nodeWidget, MouseDownEvent event, boolean editOnMouseUp) {
        final int xInCanvas = event.getX();
        final int yInCanvas = event.getY();
        Point origin = nodeWidget.theNode.getRootCoordinates(new Point(0,0));
        final int xOffset = event.getX() - origin.x;
        final int yOffset = event.getY() - origin.y;
        this.editOnMouseUp = editOnMouseUp;
        this.draggedNode = nodeWidget;
        
        event.preventDefault();
        event.stopPropagation();
        
        if (isDragging()) {
            cancelDrag();
        }
        dragHandler = scanv.addMouseMoveHandler(new MouseMoveHandler() {
            public void onMouseMove(MouseMoveEvent event) {
                event.preventDefault();
                event.stopPropagation();
                switch (dragStatus) {
                case NONE:
                    // Huh?!?
                    break;
                case WITHIN_THRESHOLD:
                    double dst = Math.sqrt((event.getX()-xInCanvas) * (event.getX()-xInCanvas) + (event.getY()-yInCanvas)*(event.getY()-yInCanvas));
                    if (dst > 6) {
                        startDragging(event, xOffset, yOffset);
                    }
                    break;
                case DRAGGING:
                    updateDragging(event.getX(), event.getY(), event.getClientX(), event.getClientY() , xOffset, yOffset);
                    break;
                }
            }
        });
        dragStatus = DragStatus.WITHIN_THRESHOLD;
    }

    public void toggleExpanded(NodeWidget nodeWidget) {
        setCollapsed(nodeWidget.getNodeID(), !isCollapsed(nodeWidget.getNodeID()));
        layout();
        
    }
    
    
    public boolean isCollapsed(int nodeID) {
        Boolean val = collapsedNodes.get(nodeID);
        
        if (val == null) {
            val = dataStore.isNodeCollapsedInitially(nodeID);
        }
        return val;
        
    }
    
    public void setCollapsed(int nodeID, boolean collapsed) {
        collapsedNodes.put(nodeID, collapsed);
    }

	public void onLoadingComplete(gwt.canvas.client.Image image) {
		if (!inLayout) {
			layout();
		}
	}


    public void setSelectionManager(NodeSelectionManager selectionManager) {
        this.selectionManager = selectionManager;
    }


    public NodeSelectionManager getSelectionManager() {
        return selectionManager;
    }


    private void setRootWidget(NodeWidget rootWidget) {
        this.rootWidget = rootWidget;
    }


    public NodeWidget getRootWidget() {
        return rootWidget;
    }


    public void addEditor(Widget editor, int x, int y, int width, int height) {
        editor.setWidth(width + "px");
        editor.setHeight(height + "px");
        // TODO fix me
        scanv.addAbsolute(editor, x, y);
    }
    
    public void removeEditor(Widget editor) {
        scanv.removeAbsolute(editor);
    }
    

    
    private class MouseRedirector implements MouseDownHandler, MouseUpHandler, MouseMoveHandler, ContextMenuHandler {
        

        public void onMouseDown(MouseDownEvent event) {
            if (event.getNativeButton() == com.google.gwt.dom.client.NativeEvent.BUTTON_LEFT) {
                GraphicComponent comp = findDeepest(event);
                if (comp != null) {
                	NodeWidget isNode = getNodeWidgetParent(comp);
                	if (isNode != null) {
                        boolean editOnMouseUp = isNode.isSelected();
                        getSelectionManager().select(isNode.getNodeID());
                        // fixFocus();
                        registerForDrag(isNode, event, editOnMouseUp);
                	}
                }
            }
        }

        public void onMouseUp(MouseUpEvent event) {
            if (event.getNativeButton() == com.google.gwt.dom.client.NativeEvent.BUTTON_LEFT) {
                GraphicComponent comp = findDeepest(event);
                boolean wasDragging = dragStatus == DragStatus.WITHIN_THRESHOLD;
                boolean shouldEdit = editOnMouseUp;
                cancelDrag();
                if (wasDragging) {
                    if (shouldEdit) {
                        getNodeWidget(selectionManager.getSelected()).startEditing();
                    } else {
                        scanv.setFocus(true);
                    }
                } else if (comp != null) {
                	if (comp instanceof CollapseExpandButton) {
                		toggleExpanded(((CollapseExpandButton) comp).getNode());
                	}
                	
                	// We've got nothing else....
                	
                } else {
                    selectionManager.select(-1);
                }
            }
        }

		public void onMouseMove(MouseMoveEvent event) {
            GraphicComponent comp = findDeepest(event);
            
            // Walk up the tree looking for somebody who has a tooltip.
            while (comp != null) {
            	if (comp.isTooltipEnabled()) {
            		resetTooltipTimer(comp, event.getClientX(), event.getClientY()+5);
            		break;
            	}
            	comp = comp.getParent();
            }
            // If we reach the top, there is no tooltip.
            if (comp == null) {
            	stopTooltipTimer();
            }
			
		}

        public void onContextMenu(ContextMenuEvent event) {
            // Convert the client X,Y into canvas co-ordinates
            int x = event.getNativeEvent().getClientX() - scanv.getAbsoluteLeft() + scanv.getHorizontalScrollPosition();
            int y = event.getNativeEvent().getClientY() - scanv.getAbsoluteTop() + Window.getScrollTop();
            
            GraphicComponent comp = findDeepest(x, y);
            if (comp != null) {
                NodeWidget isNode = getNodeWidgetParent(comp);
                if (isNode != null) {
                    getSelectionManager().select(isNode.getNodeID());

                    contextPopup = new PopupPanel(true);
                    contextPopup.addStyleName("iconsPopup");
                    final VerticalPanel iconsPanel = new VerticalPanel();
                    
                    for (Action action: actions) {
                        iconsPanel.add(action.createMenuItem(contextPopup));
                    }
                    
                    contextPopup.add(iconsPanel);
                    contextPopup.setPopupPosition(event.getNativeEvent().getClientX(), event.getNativeEvent().getClientY() + Window.getScrollTop());
                    contextPopup.show();
                    event.preventDefault();
                    event.stopPropagation();
                }
            }
        }
    }



}
